#include "qrender/sphere_renderer.h"

#include <GL/gl.h>

#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>

#include <QtMath>

#include <qrender/camera.h>

SphereRenderer::SphereRenderer() : Renderer()
{
}

SphereRenderer::~SphereRenderer()
{
    SphereRenderer::release();
}

void SphereRenderer::setPosition(const QVector3D &pos)
{
    if (pos_ == pos) {
        return;
    }

    pos_ = pos;
    data_changed_ = true;
}

void SphereRenderer::setShape(float radius, int latitudes, int longitudes,
                              QVector4D color)
{
    if (radius_ == radius && latitudes_ == latitudes &&
        longitudes_ == longitudes && color_ == color) {
        return;
    }

    radius_ = radius;
    latitudes_ = latitudes;
    longitudes_ = longitudes;
    color_ = color;

    data_changed_ = true;
}

void SphereRenderer::create()
{
    // 顶点着色器
    const char *vsrc = R"(
#version 330

in vec3 v_pos;

uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;

void main()
{
    gl_Position = projection_matrix * view_matrix * model_matrix * vec4(v_pos, 1.0);
}
)";
    // 片段着色器
    const char *fsrc = R"(
#version 330 core

uniform vec4 f_color;

out vec4 frag_color;

void main()
{
    frag_color = f_color;
}
)";

    // 创建着色器程序
    program_.reset(new QOpenGLShaderProgram());
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
                                               vsrc); // 顶点着色器
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
                                               fsrc); // 片段着色器
    program_->link();
    program_->bind();

    program_->setUniformValue("view_matrix", QMatrix4x4());
    program_->setUniformValue("projection_matrix", QMatrix4x4());
    program_->setUniformValue("f_color", color_);

    vao_.reset(new QOpenGLVertexArrayObject());
    vao_->create();

    // 生成顶点数据
    generateSphereVertices(radius_, latitudes_, longitudes_);
    vbo_.reset(new QOpenGLBuffer());
    vbo_->create();
    vbo_->bind();

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());

        // 告知 OpenGL 如何解析数据, 数据解析格式
        GLuint v_pos = program_->attributeLocation("v_pos");
        program_->setAttributeBuffer(v_pos, GL_FLOAT, 0, 3,
                                     3 * sizeof(GLfloat));
        program_->enableAttributeArray(v_pos);
    }

    program_->release();

    vbo_->release();
}

void SphereRenderer::render(const CameraInfo &camera)
{
    // 更新 OpenGL 配置
    auto f = QOpenGLContext::currentContext()->functions();
    f->glEnable(GL_DEPTH_TEST);
    f->glEnable(GL_POLYGON_SMOOTH);
    f->glDisable(GL_CULL_FACE);

    // 绑定着色器和缓冲区到当前上下文
    program_->bind();
    vbo_->bind();

    // 相机参数
    if (camera.valid) {
        program_->setUniformValue("view_matrix", camera.view_matrix);
        program_->setUniformValue("projection_matrix",
                                  camera.projection_matrix);
    }

    // 模型数据
    QMatrix4x4 model_matrix;
    model_matrix.translate(pos_);
    program_->setUniformValue("model_matrix", model_matrix);
    program_->setUniformValue("f_color", color_);

    if (data_changed_) {
        generateSphereVertices(radius_, latitudes_, longitudes_);

        data_changed_ = false;
    }

    {
        // 绘制原点
        vbo_->allocate(vertices_.constData(),
                       vertices_.size() * sizeof(QVector3D));
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());
        f->glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_INT,
                          indices_.constData());
    }

    vbo_->release();
    program_->release();
}

void SphereRenderer::release()
{
    vao_.reset();
    vbo_.reset();
    program_.reset();
}

void SphereRenderer::generateSphereVertices(float radius, int latitudes,
                                            int longitudes)
{
    vertices_.clear();
    indices_.clear();

    // 计算球体顶点坐标
    float theta, phi;
    float x, y, z;

    for (int lat = 0; lat <= latitudes; ++lat) {
        theta = static_cast<float>(lat) * M_PI / static_cast<float>(latitudes);
        auto sint = sin(theta);
        auto cost = cos(theta);

        for (int lon = 0; lon <= longitudes; ++lon) {
            phi = static_cast<float>(lon) * 2.0f * M_PI /
                  static_cast<float>(longitudes);
            auto cosp = cos(phi);
            auto sinp = sin(phi);

            // 计算球体上该点的坐标
            x = radius * sint * cosp;
            y = radius * cost;
            z = radius * sint * sinp;

            // 存储顶点坐标到VBO中
            vertices_ << QVector3D(x, y, z);
        }
    }

    // 生成球的 indices
    for (int i = 0; i < latitudes; i++) {
        for (int j = 0; j < longitudes; j++) {
            indices_.push_back(i * (longitudes + 1) + j);
            indices_.push_back((i + 1) * (longitudes + 1) + j);
            indices_.push_back((i + 1) * (longitudes + 1) + j + 1);
            indices_.push_back(i * (longitudes + 1) + j);
            indices_.push_back((i + 1) * (longitudes + 1) + j + 1);
            indices_.push_back(i * (longitudes + 1) + j + 1);
        }
    }
}
